﻿' .NET 3.5, Visual Studio 2008 SP1, Windows XP or better...

Imports System
Imports System.Text
Imports System.Runtime.InteropServices
Imports System.Windows
Imports System.Windows.Interop


''' <summary>
''' Represents a common dialog box (Win32::SHBrowseForFolder()) that allows a user to select a folder.
''' </summary>
Public Class BrowseForFolderDialog
#Region "Public Properties"

	''' <summary>
	''' Gets the current and or final selected folder path.
	''' </summary>
	Private privateSelectedFolder As String
	Public Property SelectedFolder() As String
		Get
			Return privateSelectedFolder
		End Get
		Protected Set(ByVal value As String)
			privateSelectedFolder = value
		End Set
	End Property
	''' <summary>
	''' Gets or sets the string that is displayed above the tree view control in the dialog box (must set BEFORE calling ShowDialog()). 
	''' </summary>
	Public Property Title() As String
		Get
			Return BrowseInfo.lpszTitle
		End Get
		Set(ByVal value As String)
			BrowseInfo.lpszTitle = value
		End Set
	End Property
	''' <summary>
	''' Gets or sets the initially selected folder path.
	''' </summary>
	Private privateInitialFolder As String
	Public Property InitialFolder() As String
		Get
			Return privateInitialFolder
		End Get
		Set(ByVal value As String)
			privateInitialFolder = value
		End Set
	End Property
	''' <summary>
	''' Gets or sets the initially selected and expanded folder path.  Overrides SelectedFolder.
	''' </summary>
	Private privateInitialExpandedFolder As String
	Public Property InitialExpandedFolder() As String
		Get
			Return privateInitialExpandedFolder
		End Get
		Set(ByVal value As String)
			privateInitialExpandedFolder = value
		End Set
	End Property
	''' <summary>
	''' Gets or sets the text for the dialog's OK button.
	''' </summary>
	Private privateOKButtonText As String
	Public Property OKButtonText() As String
		Get
			Return privateOKButtonText
		End Get
		Set(ByVal value As String)
			privateOKButtonText = value
		End Set
	End Property
	Private browseInfo_Renamed As BROWSEINFOW
	''' <summary>
	''' Provides direct access to the Win32::SHBrowseForFolder() BROWSEINFO structure used to create the dialog in ShowDialog().
	''' </summary>
	Public Property BrowseInfo() As BROWSEINFOW
		Get
			Return browseInfo_Renamed
		End Get
		Protected Set(ByVal value As BROWSEINFOW)
			browseInfo_Renamed = value
		End Set
	End Property
	''' <summary>
	''' Provides direct access to the ulFlags field of the Win32::SHBrowseForFolder() structure used to create the dialog in ShowDialog().
	''' </summary>
	Public Property BrowserDialogFlags() As BrowseInfoFlags
		Get
			Return BrowseInfo.ulFlags
		End Get
		Set(ByVal value As BrowseInfoFlags)
			BrowseInfo.ulFlags = value
		End Set
	End Property

#End Region


#Region "Public Constructors"

	''' <summary>
	''' Constructs a BrowseForFolderDialog with default BrowseInfoFlags set to BIF_NEWDIALOGSTYLE.
	''' </summary>
	Public Sub New()
		BrowseInfo = New BROWSEINFOW()
		BrowseInfo.hwndOwner = IntPtr.Zero
		BrowseInfo.pidlRoot = IntPtr.Zero
		BrowseInfo.pszDisplayName = New String(" "c, 260)
		BrowseInfo.lpszTitle = "Select a folder:"
		BrowseInfo.ulFlags = BrowseInfoFlags.BIF_NEWDIALOGSTYLE
		BrowseInfo.lpfn = New BrowseCallbackProc(AddressOf BrowseEventHandler)
		BrowseInfo.lParam = IntPtr.Zero
		BrowseInfo.iImage = -1
	End Sub

#End Region


#Region "Public ShowDialog() Overloads"

	''' <summary>
	''' Shows the dialog (Win32::SHBrowseForFolder()).
	''' </summary>
	Public Function ShowDialog() As Nullable(Of Boolean)
		Return PInvokeSHBrowseForFolder(Nothing)
	End Function

	''' <summary>
	''' Shows the dialog (Win32::SHBrowseForFolder()) with its hwndOwner set to the handle of 'owner'.
	''' </summary>
	Public Function ShowDialog(ByVal owner As Window) As Nullable(Of Boolean)
		Return PInvokeSHBrowseForFolder(owner)
	End Function

#End Region


#Region "PInvoke Stuff"

	Private Function PInvokeSHBrowseForFolder(ByVal owner As Window) As Nullable(Of Boolean)
		Dim windowhelper As WindowInteropHelper
		If Nothing IsNot owner Then
			windowhelper = New WindowInteropHelper(owner)
			BrowseInfo.hwndOwner = windowhelper.Handle
		End If

		Dim pidl As IntPtr = SHBrowseForFolderW(browseInfo_Renamed)

		If IntPtr.Zero <> pidl Then
			Dim pathsb As New StringBuilder(260)
			If False <> SHGetPathFromIDList(pidl, pathsb) Then
				SelectedFolder = pathsb.ToString()
				Marshal.FreeCoTaskMem(pidl)
				Return True
			End If
		End If

		Return False
	End Function

	Private Function BrowseEventHandler(ByVal hwnd As IntPtr, ByVal uMsg As MessageFromBrowser, ByVal lParam As IntPtr, ByVal lpData As IntPtr) As Integer
		Select Case uMsg
			Case MessageFromBrowser.BFFM_INITIALIZED
				' The dialog box has finished initializing.
				' lParam   Not used, value is NULL.

				If (Not String.IsNullOrEmpty(InitialExpandedFolder)) Then
					SendMessageW(hwnd, MessageToBrowser.BFFM_SETEXPANDED, New IntPtr(1), InitialExpandedFolder)
				ElseIf (Not String.IsNullOrEmpty(InitialFolder)) Then
					SendMessageW(hwnd, MessageToBrowser.BFFM_SETSELECTIONW, New IntPtr(1), InitialFolder)
				End If

				If (Not String.IsNullOrEmpty(OKButtonText)) Then
					SendMessageW(hwnd, MessageToBrowser.BFFM_SETOKTEXT, New IntPtr(1), OKButtonText)
				End If

				Exit Select
			Case MessageFromBrowser.BFFM_SELCHANGED
				' The selection has changed in the dialog box.
				' lParam   A pointer to an item identifier list (PIDL) identifying the newly selected item.

				Dim pathsb As New StringBuilder(260)
				If False <> SHGetPathFromIDList(lParam, pathsb) Then
					SelectedFolder = pathsb.ToString()
				End If

				Exit Select
			Case MessageFromBrowser.BFFM_VALIDATEFAILEDA ' ANSI
				' The user typed an invalid name into the dialog's edit box. A nonexistent folder is considered an invalid name.
				' lParam   A pointer to a string containing the invalid name. An application can use this data in an error dialog informing the user that the name was not valid.
				' Return zero to dismiss the dialog or nonzero to keep the dialog displayed
				Exit Select
			Case MessageFromBrowser.BFFM_VALIDATEFAILEDW ' Unicode
				' The user typed an invalid name into the dialog's edit box. A nonexistent folder is considered an invalid name.
				' lParam   A pointer to a string containing the invalid name. An application can use this data in an error dialog informing the user that the name was not valid.
				' Return zero to dismiss the dialog or nonzero to keep the dialog displayed
				Exit Select
			Case MessageFromBrowser.BFFM_IUNKNOWN
				' An IUnknown interface is available to the dialog box.
				' lParam   A pointer to an IUnknown interface.
				Exit Select
		End Select

		Return 0
	End Function

	Public Delegate Function BrowseCallbackProc(ByVal hwnd As IntPtr, ByVal uMsg As MessageFromBrowser, ByVal lParam As IntPtr, ByVal lpData As IntPtr) As Integer

	<Flags()> _
	Public Enum BrowseInfoFlags As UInteger
		''' <summary>
		''' No specified BIF_xxx flags.
		''' </summary>
		BIF_None = &H0
		''' <summary>
		''' Only return file system directories. If the user selects folders that are not part of the file system, the OK button is grayed.
		''' </summary>
		BIF_RETURNONLYFSDIRS = &H1 ' For finding a folder to start document searching
		''' <summary>
		''' Do not include network folders below the domain level in the dialog box's tree view control.
		''' </summary>
		BIF_DONTGOBELOWDOMAIN = &H2	' For starting the Find Computer
		''' <summary>
		''' Include a status area in the dialog box. 
		''' </summary>
		BIF_STATUSTEXT = &H4 ' Top of the dialog has 2 lines of text for BROWSEINFO.lpszTitle and one line if
		' this flag is set.  Passing the message BFFM_SETSTATUSTEXTA to the hwnd can set the
		' rest of the text.  This is not used with BIF_USENEWUI and BROWSEINFO.lpszTitle gets
		' all three lines of text.
		''' <summary>
		''' Only return file system ancestors. An ancestor is a subfolder that is beneath the root folder in the namespace hierarchy.
		''' </summary>
		BIF_RETURNFSANCESTORS = &H8
		''' <summary>
		''' Include an edit control in the browse dialog box that allows the user to type the name of an item.
		''' </summary>
		BIF_EDITBOX = &H10 ' Add an editbox to the dialog
		''' <summary>
		''' If the user types an invalid name into the edit box, the browse dialog box will call the application's BrowseCallbackProc with the BFFM_VALIDATEFAILED message. 
		''' </summary>
		BIF_VALIDATE = &H20	' insist on valid result (or CANCEL)
		''' <summary>
		''' Use the new user interface. Setting this flag provides the user with a larger dialog box that can be resized.
		''' </summary>
		BIF_NEWDIALOGSTYLE = &H40	' Use the new dialog layout with the ability to resize
		' Caller needs to call OleInitialize() before using this API
		''' <summary>
		''' Use the new user interface, including an edit box. This flag is equivalent to BIF_EDITBOX | BIF_NEWDIALOGSTYLE. 
		''' </summary>
		BIF_USENEWUI = BIF_NEWDIALOGSTYLE Or BIF_EDITBOX
		''' <summary>
		''' The browse dialog box can display URLs. The BIF_USENEWUI and BIF_BROWSEINCLUDEFILES flags must also be set. 
		''' </summary>
		BIF_BROWSEINCLUDEURLS = &H80 ' Allow URLs to be displayed or entered. (Requires BIF_USENEWUI)
		''' <summary>
		''' When combined with BIF_NEWDIALOGSTYLE, adds a usage hint to the dialog box in place of the edit box.
		''' </summary>
		BIF_UAHINT = &H100 ' Add a UA hint to the dialog, in place of the edit box. May not be combined with BIF_EDITBOX
		''' <summary>
		''' Do not include the New Folder button in the browse dialog box.
		''' </summary>
		BIF_NONEWFOLDERBUTTON = &H200	' Do not add the "New Folder" button to the dialog. Only applicable with BIF_NEWDIALOGSTYLE.
		''' <summary>
		''' When the selected item is a shortcut, return the PIDL of the shortcut itself rather than its target.
		''' </summary>
		BIF_NOTRANSLATETARGETS = &H400 ' don't traverse target as shortcut
		''' <summary>
		''' Only return computers. If the user selects anything other than a computer, the OK button is grayed.
		''' </summary>
		BIF_BROWSEFORCOMPUTER = &H1000 ' Browsing for Computers.
		''' <summary>
		''' Only allow the selection of printers. If the user selects anything other than a printer, the OK button is grayed. 
		''' </summary>
		BIF_BROWSEFORPRINTER = &H2000	' Browsing for Printers
		''' <summary>
		''' The browse dialog box will display files as well as folders.
		''' </summary>
		BIF_BROWSEINCLUDEFILES = &H4000	' Browsing for Everything
		''' <summary>
		''' The browse dialog box can display shareable resources on remote systems. 
		''' </summary>
		BIF_SHAREABLE = &H8000 ' sharable resources displayed (remote shares, requires BIF_USENEWUI)
	End Enum

	<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
	Public Class BROWSEINFOW
		''' <summary>
		''' A handle to the owner window for the dialog box.
		''' </summary>
		Public hwndOwner As IntPtr
		''' <summary>
		''' A pointer to an item identifier list (PIDL) specifying the location of the root folder from which to start browsing. 
		''' </summary>
		Public pidlRoot As IntPtr	' PCIDLIST_ABSOLUTE
		''' <summary>
		''' The address of a buffer to receive the display name of the folder selected by the user. The size of this buffer is assumed to be MAX_PATH characters.
		''' </summary>
		Public pszDisplayName As String	' Output parameter! (length must be >= MAX_PATH)
		''' <summary>
		''' The address of a null-terminated string that is displayed above the tree view control in the dialog box. 
		''' </summary>
		Public lpszTitle As String
		''' <summary>
		''' Flags specifying the options for the dialog box. 
		''' </summary>
		Public ulFlags As BrowseInfoFlags
		''' <summary>
		''' A BrowseCallbackProc delegate that the dialog box calls when an event occurs.
		''' </summary>
		Public lpfn As BrowseCallbackProc
		''' <summary>
		''' An application-defined value that the dialog box passes to the BrowseCallbackProc delegate, if one is specified.
		''' </summary>
		Public lParam As IntPtr
		''' <summary>
		''' A variable to receive the image associated with the selected folder. The image is specified as an index to the system image list.
		''' </summary>
		Public iImage As Integer ' Output parameter!
	End Class

	' message from browser
	Public Enum MessageFromBrowser As UInteger
		''' <summary>
		''' The dialog box has finished initializing.
		''' </summary>
		BFFM_INITIALIZED = 1
		''' <summary>
		''' The selection has changed in the dialog box.
		''' </summary>
		BFFM_SELCHANGED = 2
		''' <summary>
		''' (ANSI) The user typed an invalid name into the dialog's edit box. A nonexistent folder is considered an invalid name.
		''' </summary>
		BFFM_VALIDATEFAILEDA = 3
		''' <summary>
		''' (Unicode) The user typed an invalid name into the dialog's edit box. A nonexistent folder is considered an invalid name.
		''' </summary>
		BFFM_VALIDATEFAILEDW = 4
		''' <summary>
		''' An IUnknown interface is available to the dialog box.
		''' </summary>
		BFFM_IUNKNOWN = 5
	End Enum

	' messages to browser
	Public Enum MessageToBrowser As UInteger

		''' <summary>
		''' Win32 API macro - start of user defined window message range.
		''' </summary>
		WM_USER = &H400
		''' <summary>
		''' (ANSI) Sets the status text. Set lpData to point to a null-terminated string with the desired text. 
		''' </summary>
		BFFM_SETSTATUSTEXTA = WM_USER + 100
		''' <summary>
		''' Enables or disables the dialog box's OK button.  lParam - To enable, set to a nonzero value. To disable, set to zero.
		''' </summary>
		BFFM_ENABLEOK = WM_USER + 101
		''' <summary>
		''' (ANSI) Specifies the path of a folder to select. 
		''' </summary>
		BFFM_SETSELECTIONA = WM_USER + 102
		''' <summary>
		''' (Unicode) Specifies the path of a folder to select. 
		''' </summary>
		BFFM_SETSELECTIONW = WM_USER + 103
		''' <summary>
		''' (Unicode) Sets the status text. Set lpData to point to a null-terminated string with the desired text. 
		''' </summary>
		BFFM_SETSTATUSTEXTW = WM_USER + 104
		''' <summary>
		''' Sets the text that is displayed on the dialog box's OK button.
		''' </summary>
		BFFM_SETOKTEXT = WM_USER + 105 ' Unicode only
		''' <summary>
		''' Specifies the path of a folder to expand in the Browse dialog box. 
		''' </summary>
		BFFM_SETEXPANDED = WM_USER + 106 ' Unicode only
	End Enum

	<DllImport("shell32.dll")> _
	Private Shared Function SHBrowseForFolderW(<MarshalAs(UnmanagedType.LPStruct), [In](), Out()> ByVal bi As BROWSEINFOW) As IntPtr
	End Function
	<DllImport("shell32.dll")> _
	Private Shared Function SHGetPathFromIDList(ByVal pidl As IntPtr, ByVal path As StringBuilder) As Boolean
	End Function
	<DllImport("user32.dll")> _
	Public Shared Function SendMessageW(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
	End Function
	<DllImport("user32.dll")> _
	Public Shared Function SendMessageW(ByVal hWnd As IntPtr, ByVal msg As MessageToBrowser, ByVal wParam As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> ByVal str As String) As IntPtr
	End Function

#End Region
End Class
